package com.ep.thirdPlatformProject.maycur.helper;


import cn.hutool.core.date.DateTime;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.StopWatch;
import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson.JSON;
import com.ep.thirdPlatformProject.maycur.entity.MayCurCommonBillDetail;
import com.ep.thirdPlatformProject.maycur.entity.MayCurCommonBillMain;
import com.ep.thirdPlatformProject.maycur.entity.MayCurCommonResponse;
import com.ep.thirdPlatformProject.maycur.entity.login.MayCurToken;
import com.ep.thirdPlatformProject.system.config.maycur.MayCurProperties;
import com.ep.thirdPlatformProject.system.util.CaffeineCacheUtils;
import com.ep.thirdPlatformProject.system.util.OkHttpUtils;
import com.xxl.job.core.context.XxlJobHelper;
import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.springframework.retry.annotation.Retryable;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;
import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.IntStream;

import static com.ep.thirdPlatformProject.maycur.common.Constant.*;

@Component
@Slf4j
@RequiredArgsConstructor
public class MayCurCommonHelper {

    private final MayCurProperties mayCurProperties;

    /**
     * 构造url
     *
     * @param apiUrl isurl
     * @return {@link String}
     */
    public String constructUrl(@NotNull String apiUrl) {
        // 实现构造获取Token的URL逻辑
        return mayCurProperties.getUrl() + apiUrl;
    }

    /**
     * 生成应用秘密
     *
     * @param timestamp 时间戳
     * @return {@link String}
     */
    public String generateAppSecret(Long timestamp) {

        // 实现基于时间戳、appCode和appSecretOrigin生成加密字符串的逻辑
        return DigestUtils.sha256Hex(mayCurProperties.getAppSecretOrigin() + ":" + mayCurProperties.getAppCode() + ":" + timestamp);
    }

    /**
     * 返回成功吗?
     *
     * @param response 响应
     * @return boolean
     */
    public boolean isReturnSuccessful(@NotNull String response) {
        // 检查响应是否表示登录成功的逻辑
        return !response.contains("\"success\":true");
    }


    /**
     * 解析对象响应
     *
     * @param response 响应
     * @param clazz    clazz
     * @return {@link T}
     */
    public <T> T parseResponseToObject(String response, Class<T> clazz) {
        // 解析响应，返回Token对象的逻辑
        MayCurCommonResponse commonResponse = JSON.parseObject(response, MayCurCommonResponse.class);
        if (!commonResponse.getSuccess()) {
            throw new MayCurCommonAcquisitionException("解析返回数据失败!" + commonResponse.getMessage());
        }
        return JSON.parseObject(JSON.toJSONString(commonResponse.getData()), clazz);
    }


    /**
     * 解析响应到列表
     *
     * @param response 响应
     * @param clazz    clazz
     * @return {@link List}<{@link T}>
     */
    public <T> List<T> parseResponseToList(String response, Class<T> clazz) {
        // 解析响应，返回Token对象的逻辑
        MayCurCommonResponse commonResponse = JSON.parseObject(response, MayCurCommonResponse.class);
        if (!commonResponse.getSuccess()) {
            throw new MayCurCommonAcquisitionException("解析返回数据失败!" + commonResponse.getMessage());
        }
        return JSON.parseArray(JSON.toJSONString(commonResponse.getData()), clazz);
    }


    /**
     * 是在周期内
     *
     * @param time      时间
     * @param startTime 开始时间
     * @param endTime   结束时间
     * @return boolean
     */
    public boolean isInPeriod(long time, long startTime, long endTime) {
        return time >= startTime && time <= endTime;
    }


    /**
     * 平均分配流
     *
     * @param source 源
     * @param n      n
     * @return {@link List}<{@link List}<{@link T}>>
     */
    public <T> List<List<T>> averageAssignStream(List<T> source, int n) {
        return IntStream.range(0, (source.size() + n - 1) / n)
                .mapToObj(i -> source.subList(i * n, Math.min(source.size(), (i + 1) * n))).toList();
    }


    /**
     * 发送公共列表请求和解析响应
     *
     * @param url           请求的URL地址
     * @param tokenSupplier 令牌供应商 ->token 获取
     * @param tClass        需要转化的类
     * @param pageNo        页面编号
     * @param paramFunc     参数函数, 需要传入pageNo
     * @return 返回请求的响应结果，以字符串形式呈现
     */
    @Retryable(maxAttempts = 3, retryFor = {
            MayCurLoginHelper.TokenAcquisitionException.class,
            OkHttpUtils.OKHttpRequestException.class
    })
    public <T extends MayCurCommonBillMain<? extends MayCurCommonBillDetail>> T sendCommonListRequestAndParseResponse(
            @NotNull String url, Class<T> tClass, @NotNull Integer pageNo,
            @NotNull Function<Integer, Object> paramFunc,
            Supplier<MayCurToken> tokenSupplier
    ) {
        try {
            // 从缓存中尝试获取登录令牌，如果不存在则重新登录并更新缓存
            MayCurToken token = CaffeineCacheUtils.getSync(LOGIN_TOKEN_CACHE_KEY, MayCurToken.class);

            if (ObjectUtil.isNull(token)) {
                // 登录过程
                token = tokenSupplier.get();
                // 将登录获取的令牌存入缓存,默认为30分钟过期时间，不考虑极限情况下token过期而cache未更新
                // 引入retry机制
                CaffeineCacheUtils.putSync(LOGIN_TOKEN_CACHE_KEY, token);
            }

            // 使用OkHttp发送带有登录令牌和企业代码的报销清单请求，并同步返回响应结果
            String response = OkHttpUtils.builder()
                    .url(url)
                    .addJsonParam(paramFunc.apply(pageNo))
                    .addHeader("tokenId", token.getTokenId())
                    .addHeader("entCode", token.getEntCode())
                    .post(true)
                    .sync();

            if (isReturnSuccessful(response)) {
                log.error("获取列表失败! 结果为：{}", response);
                throw new MayCurCommonAcquisitionException("获取通用列表失败");
            }
            return parseResponseToObject(response, tClass);
        } catch (MayCurLoginHelper.TokenAcquisitionException var1) {
            log.error("token获取失败原因是{}", var1.getMessage(), var1);
            throw new MayCurLoginHelper.TokenAcquisitionException("token获取失败！", var1);
        } catch (OkHttpUtils.OKHttpRequestException var2) {
            log.error("request请求异常原因是{}", var2.getMessage(), var2);
            throw new OkHttpUtils.OKHttpRequestException("request请求异常！", var2);
        } catch (MayCurCommonAcquisitionException var3) {
            log.error("获取通用列表失败{}", var3.getMessage(), var3);
            throw new OkHttpUtils.OKHttpRequestException("获取通用列表失败！", var3);
        } catch (Exception var4) {
            log.error("未知异常原因是{}", var4.getMessage(), var4);
            throw new RuntimeException("request请求异常！", var4);
        }
    }

    /**
     * 将响应集合解析为列表
     *
     * @param tClass        t类
     * @param url           url
     * @param tokenSupplier 牌供应商
     * @param paramSupplier param供应商
     * @return {@link List}<{@link T}>
     */
    @SuppressWarnings("unchecked")
    public <T extends MayCurCommonBillDetail> List<T> parseResponseCollecttionToList(Class<? extends MayCurCommonBillMain<T>> tClass, @NotNull String url,
                                                                                     Supplier<MayCurToken> tokenSupplier,
                                                                                     @NotNull Function<Integer, Object> paramSupplier
    ) {
        int pageNo = OFFSET_PAGE_NO;
        List<T> list = new ArrayList<>();
        try {
            MayCurCommonBillMain<? extends MayCurCommonBillDetail> dto = sendCommonListRequestAndParseResponse(
                    url, tClass, pageNo, paramSupplier,
                    tokenSupplier
            );

            if (dto != null && dto.getList() != null && !dto.getList().isEmpty()) {
                // 增加了类型安全检查
                list.addAll((Collection<? extends T>) dto.getList());
                Boolean hasNextPage = dto.getHasNextPage();
                if (Boolean.TRUE.equals(hasNextPage)) {
                    // 使用循环代替递归来避免栈溢出的风险
                    while (BooleanUtil.isTrue(hasNextPage)) {
                        pageNo += 1;

                        MayCurCommonBillMain<? extends MayCurCommonBillDetail> nextDto = sendCommonListRequestAndParseResponse(url, tClass, pageNo, paramSupplier,
                                tokenSupplier);
                        if (nextDto != null && nextDto.getList() != null && !nextDto.getList().isEmpty()) {
                            // 增加了类型安全检查
                            list.addAll((Collection<? extends T>) nextDto.getList());
                        }
                        if ((pageNo & 0b11111) == 0) {
                            log.info("执行次数达到32的倍数了，休息500ms吧");
                            ThreadUtil.sleep(500);
                        }
                        if (ObjectUtil.isNull(nextDto) || !BooleanUtil.isTrue(hasNextPage = nextDto.getHasNextPage())) {
                            // 如果没有下一个页面，跳出循环
                            break;
                        }

                    }
                }
            }
        } catch (Exception e) {
            log.error("获取列表失败", e);
            return new ArrayList<>();
        }
        log.info("获取列表成功! 列表长度为：{}", list.size());
        return list;
    }


    /**
     * 获取公共列表金额
     *
     * @param inputList 输入列表
     * @param billName  比尔名字
     * @param executor  执行
     * @param dateTime  日期时间
     * @return {@link Map}<{@link String}, {@link BigDecimal}>
     */
    public <T extends MayCurCommonBillDetail> Map<String, BigDecimal> getCommonListAmount(List<T> inputList, String billName, DateTime dateTime, ThreadPoolTaskExecutor executor) {

        Map<String, BigDecimal> finalMap = new HashMap<>();
        BigDecimal yearAmount = ZERO; // 年报销总额初始化
        BigDecimal monthAmount = ZERO; // 月报销总额初始化
        BigDecimal dayAmount = ZERO; // 日报销总额初始化

        try {
            StopWatch stopWatch = DateUtil.createStopWatch();
            stopWatch.start("请求获取每刻" + billName);

            stopWatch.stop();
            log.info("请求获取每刻{}耗时：{}ms", billName, stopWatch.getTotalTimeMillis());
            if (inputList != null && !inputList.isEmpty()) {

                // 等分切分列表
                List<List<T>> lists = averageAssignStream(inputList, 1000);
                log.info("切分后的列表数量：{}", lists.size());

                // 初始化本月和本日的时间范围

                long monthStartTime = DateUtil.beginOfMonth(dateTime).getTime();


                // 本日时间范围修改 为昨天的当前时间到今天的当前时间
                long commonEndTime = DateUtil.date(dateTime).getTime();
                Calendar calendar = Calendar.getInstance();
                calendar.setTime(dateTime);
                calendar.add(Calendar.DAY_OF_MONTH, -1);
                long dayStartTime = calendar.getTimeInMillis();

                // 获取本年开始时间
                long yearStartTime = DateUtil.beginOfYear(dateTime).getTime();
                XxlJobHelper.log("""
                                时间日期处理如下：                               \s
                                本年开始日期 时间戳{} ： 标准化：{};
                                本月开始日期 时间戳{} ： 标准化：{};
                                本日开始日期 时间戳{} ： 标准化：{};
                                通用结束日期 时间戳{} ： 标准化：{}
                                """,
                        yearStartTime, DateUtil.date(yearStartTime),
                        monthStartTime, DateUtil.date(monthStartTime),
                        dayStartTime, DateUtil.date(dayStartTime),
                        commonEndTime, DateUtil.date(commonEndTime)
                );
                StopWatch watch = DateUtil.createStopWatch();
                watch.start("多线程处理数据");
                List<CompletableFuture<Map<String, BigDecimal>>> res = lists.stream().parallel().map(list ->
                        CompletableFuture.supplyAsync(
                                        () -> processMoney(list, monthStartTime, dayStartTime, commonEndTime), executor)
                                .handle((result, throwable) -> {
                                    if (throwable != null) {
                                        System.out.println("任务异常! e=" + throwable + ", " + new Date());
                                        log.error("任务异常! e={}, {}", throwable, new Date(), throwable);
                                    } else {
                                        return result;
                                    }
                                    return new HashMap<String, BigDecimal>();
                                })).toList();

                List<Map<String, BigDecimal>> list = res.stream().map(CompletableFuture::join).toList();

                for (Map<String, BigDecimal> map : list) {
                    yearAmount = yearAmount.add(map.get(YEAR_TOTAL_EXPENSE_AMOUNT));
                    monthAmount = monthAmount.add(map.get(MONTH_TOTAL_EXPENSE_AMOUNT));
                    dayAmount = dayAmount.add(map.get(DAY_TOTAL_EXPENSE_AMOUNT));
                }
                watch.stop();
                log.info("多线程处理数据耗时：{}ms", watch.getTotalTimeMillis());
            }
        } catch (Exception e) {
            // 报销金额获取异常处理
            log.error("获取{}金额失败! 原因是{}", billName, e.getMessage(), e);
            throw new MayCurCommonAcquisitionException("获取金额失败", e);
        }
        // 将累计的报销金额放入返回映射中
        finalMap.put(YEAR_TOTAL_EXPENSE_AMOUNT, yearAmount);
        finalMap.put(MONTH_TOTAL_EXPENSE_AMOUNT, monthAmount);
        finalMap.put(DAY_TOTAL_EXPENSE_AMOUNT, dayAmount);

        return finalMap;
    }

    /**
     * 处理金额
     *
     * @param list           列表
     * @param monthStartTime 月开始时间
     * @param dayStartTime   一天开始时间

     * @return {@link Map}<{@link String}, {@link BigDecimal}>
     */
    private <T extends MayCurCommonBillDetail> Map<String, BigDecimal> processMoney(
            List<T> list, Long monthStartTime, Long dayStartTime, Long commonEndTime
    ) {
        Map<String, BigDecimal> finalMap = new HashMap<>();
        BigDecimal yearAmountTemp = ZERO; // 年报销总额初始化
        BigDecimal monthAmountTemp = ZERO; // 月报销总额初始化
        BigDecimal dayAmountTemp = ZERO; // 日报销总额初始化
        // 遍历报销列表，累计年、月、日的报销金额
        for (T responseDTO : list) {
            yearAmountTemp = yearAmountTemp.add(responseDTO.getPaymentAmount().getAmount());
            if (isInPeriod(responseDTO.getProcInstEndTime(), monthStartTime, commonEndTime)) {
                monthAmountTemp = monthAmountTemp.add(responseDTO.getPaymentAmount().getAmount());
                if (isInPeriod(responseDTO.getProcInstEndTime(), dayStartTime, commonEndTime)) {
                    dayAmountTemp = dayAmountTemp.add(responseDTO.getPaymentAmount().getAmount());
                }
            }
        }
        finalMap.put(YEAR_TOTAL_EXPENSE_AMOUNT, yearAmountTemp);
        finalMap.put(MONTH_TOTAL_EXPENSE_AMOUNT, monthAmountTemp);
        finalMap.put(DAY_TOTAL_EXPENSE_AMOUNT, dayAmountTemp);
        return finalMap;
    }

    /**
     * 通用获取异常
     *
     * @author Karl
     * @date 2024/03/27
     */
    static class MayCurCommonAcquisitionException extends RuntimeException {
        public MayCurCommonAcquisitionException(String message) {
            super(message);
        }

        public MayCurCommonAcquisitionException(String message, Throwable cause) {
            super(message, cause);
        }
    }

}
